<?php
namespace Api\Http;
use Api\Application\Benchmark;

/**
 * Class Request
 * @package Api\Http
 * @author Steffen Brem <steffenbrem@gmail.com>
 */
class Request
{
    protected $uri;
    protected $baseUrl;
    protected $params = array();

    protected $contentType = 'application/json';

    protected $resource;
    protected $identifier;

    protected $rpc;

    protected $fields = array();

    protected $where    = array();
    protected $orWhere  = array();
    protected $whereIn  = array();

    protected $orderBy = array();

    protected $maxResults = 10;
    protected $offset;

    protected $singleResult = false;

    protected $return = false;

    protected $payload = array();

    protected $alt = 'json';

    /**
     * Constructor
     */
    public function __construct($uri = null, array $params = array())
    {
        $this->uri      = ($uri === null) ? $this->fetchUri() : $this->cleanUri($uri);
        $this->baseUrl  = $this->fetchBaseUrl();

        $this->params = (!$params) ? $_GET : $params;

        $parts = explode('/', $this->uri);

        if (isset($parts['0'])) $this->resource = $parts['0'];
        if (isset($parts['1'])) $this->identifier = $parts['1'];

        // Output format
        if (isset($this->params['alt'])) $this->alt = $this->params['alt'];

        // Remote Procedure Call
        $this->rpc = $this->fetchRpc();

        // Where statement
        if (isset($this->params['where']))
            $this->setWhere($this->parseWhere($this->params['where']));
        if (isset($this->params['orWhere']))
            $this->setOrWhere($this->parseWhere($this->params['orWhere']));
        if (isset($this->params['whereIn']))
            $this->setWhereIn($this->parseWhereIn($this->params['whereIn']));

        // Order by
        if (isset($this->params['orderBy']))
            $this->setOrderBy($this->parseOrderBy($this->params['orderBy']));

        // Offset
        if (isset($this->params['maxResults']))
            $this->maxResults = $this->params['maxResults'];
        if (isset($this->params['offset']))
            $this->offset = $this->params['offset'];

        if (isset($this->params['singleResult']))
            $this->singleResult = ($this->params['singleResult']) ? true : false;

        if (array_key_exists('return', $this->params))
            $this->setReturn(($this->params['return'] == 1) ? true : false);
    }

    public function getParam($name)
    {
        return $this->params[$name];
    }

    /**
     * @return array|mixed|string
     * @throws \Exception
     */
    public function getPayload()
    {
        if ($this->payload) return $this->payload;

        $payload = trim(file_get_contents("php://input"));

        if (!$payload)
        {
            throw new \Exception("Could not fetch input stream. Payload contains no body.");
        }

        if ($payload['0'] == '{' || $payload['0'] == '[')
        {
            $json = json_decode($payload, true);

            if (json_last_error())
            {
                throw new \Exception("Request payload contains malformed JSON data.");
            }

            return $json;
        }

        return $payload;
    }

    public function getContentType() { return $this->contentType; }
    public function setContentType($contentType) { $this->contentType = $contentType; }

    public function getReturn() { return $this->return; }
    public function setReturn($return) { $this->return = $return; }

    /**
     * @param $payload
     */
    public function setPayload($payload)
    {
        switch ($this->getContentType())
        {
            case 'application/x-www-form-urlencoded':
                parse_str($payload, $_POST);
                break;
        }

        $this->payload = $payload;
    }


    /**
     * @return string
     */
    public function getBaseUrl() { return $this->baseUrl; }

    /**
     * @return mixed
     */
    public function getResource() { return $this->resource; }

    /**
     * @param $resource
     */
    public function setResource($resource) { $this->resource = $resource; }


    /**
     * @return mixed
     */
    public function getIdentifier() { return $this->identifier; }

    /**
     * @param $identifier
     */
    public function setIdentifier($identifier) { $this->identifier = $identifier; }


    /**
     * @return string
     */
    public function getRpc() { return $this->rpc; }

    /**
     * @param $rpc
     */
    public function setRpc($rpc) { $this->rpc = $rpc; }


    /**
     * @return string
     */
    public function getAlt() { return $this->alt; }

    /**
     * @param $alt
     */
    public function setAlt($alt) { $this->alt = $alt; }


    /**
     * Get the where statement
     */
    public function getWhere() { return $this->where; }

    /**
     * @param array $where
     */
    public function setWhere(array $where) { $this->where = $where; }

    /**
     * @return array
     */
    public function getOrWhere() { return $this->orWhere; }

    /**
     * @param $orWhere
     */
    public function setOrWhere($orWhere) { $this->orWhere = $orWhere; }

    /**
     * @return array
     */
    public function getWhereIn() { return $this->whereIn; }

    /**
     * @param $whereIn
     */
    public function setWhereIn($whereIn) { $this->whereIn = $whereIn; }


    /**
     * @return array
     */
    public function getOrderBy() { return $this->orderBy; }

    /**
     * Set order by statement
     *
     *  array(
     *      'field' => 'order'
     *  );
     *
     * @param array $orderBy
     */
    public function setOrderBy(array $orderBy) { $this->orderBy = $orderBy; }


    /**
     * @return int
     */
    public function getMaxResults() { return $this->maxResults; }

    /**
     * @param $maxResults
     */
    public function setMaxResults($maxResults) { $this->maxResults = $maxResults; }


    /**
     * @return mixed
     */
    public function getOffset() { return $this->offset; }

    /**
     * @param $offset
     */
    public function setOffset($offset) { $this->offset = $offset; }


    /**
     * @return bool
     */
    public function getSingleResult() { return $this->singleResult; }

    /**
     * @param bool $singleResult
     */
    public function setSingleResult($singleResult) { $this->singleResult = $singleResult; }

    /**
     * Parse where statement
     *
     * @param $data
     * @return array
     * @throws \Exception
     */
    protected function parseWhere($data)
    {
        if (!is_array($data))
        {
            throw new \Exception("Where statement in your uri must be an array of the following format: <i>http://www.example.org/resource?where[name]=foo&where[age]=24</i>.");
        }

        foreach ($data as $field => $value)
        {
            $values = array_map('trim', explode(',', $value));

            if (count($values) > 1)
            {
                $data[$field] = $values;
            }
        }

        return $data;
    }

    /**
     * @param $data
     * @return array
     */
    protected function parseWhereIn($data)
    {
        $data = $this->parseWhere($data);

        foreach ($data as $field => $value)
        {
            $values = array_map('trim', explode(',', $value));
            $data[$field] = $values;
        }

        return $data;
    }


    /**
     * Parse order by statement
     *
     * @param $data
     * @return array
     */
    public function parseOrderBy($data)
    {
        $orderBy = array();
        $fields = array_map('trim', explode(',', $data));

        foreach ($fields as $field)
        {
            $parts = array_map('trim', explode(':', $field));
            $field = $parts['0'];
            $order = 'a';

            if (isset($parts['1']))
            {
                $order = ($parts['1'] == 'd') ? 'd' : 'a';
            }

            $orderBy[$field] = $order;
        }

        return $orderBy;
    }

    /**
     * Fetch the Remote Procedure Call
     *
     * @return string
     * @throws \Exception
     */
    protected function fetchRpc()
    {
        if (isset($this->params['rpc']))
        {
            $rpc = $this->params['rpc'];
        }
        else
        {
            switch ($_SERVER['REQUEST_METHOD'])
            {
                case "GET":
                    $rpc = "__read";
                    break;
                case "POST":
                    $rpc = "__create";
                    break;
                case "PUT":
                    $rpc = "__update";
                    break;
                case "DELETE":
                    $rpc = "__delete";
                    break;
                case "PATCH":
                    $rpc = "__partialUpdate";
                    break;
                case "HEAD":
                    $rpc = "__header";
                    break;
                case "OPTIONS":
                    $rpc = "__options";
                    break;
                default:
                    throw new \Exception("HTTP method '" . $_SERVER['REQUEST_METHOD'] . "' not implemented");
            }
        }

        return $rpc;
    }

    /**
     * Fetch base url
     *
     * @return string
     */
    protected function fetchBaseUrl()
    {
        $protocol = strtolower(substr($_SERVER["SERVER_PROTOCOL"], 0, 5)) == 'https' ? 'https://' : 'http://';

        $path = $_SERVER['SCRIPT_NAME'];

        $path_parts = pathinfo($path);

        $directory = $path_parts['dirname'];
        $directory = ($directory == "/") ? "" : $directory;

        $host = $_SERVER['HTTP_HOST'];

        return $protocol . $host . $directory;
    }


    /**
     * Clean the Uri so we can work with it
     *
     * @param $uri
     * @return mixed
     */
    protected function cleanUri($uri)
    {
        return parse_url(trim(urldecode($uri), '/'), PHP_URL_PATH);
    }

    /**
     * Fetches Uri from PATH_INFO or REQUEST_URI
     *
     * @return string
     */
    protected function fetchUri()
    {
        $uri = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');

        if ($uri != '' && $uri != '/')
        {
            return $this->cleanUri($uri);
        }

        // PATH_INFO did not work, use REQUEST_URI as last resort
        $uri = urldecode($_SERVER['REQUEST_URI']);

        if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
        {
            $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
        }
        else if (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
        {
            $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
        }

        // Fix the $this->params vars
        $parts = explode('?', $uri, 2);
        $uri = $parts['0'];

        return $this->cleanUri($uri);
    }
}